package com.education.platform.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.education.platform.dto.auth.*;
import com.education.platform.entity.User;
import com.education.platform.entity.UserAuthLog;
import com.education.platform.entity.SmsCode;
import com.education.platform.exception.BusinessException;
import com.education.platform.mapper.UserMapper;
import com.education.platform.mapper.UserAuthLogMapper;
import com.education.platform.mapper.SmsCodeMapper;
import com.education.platform.service.AuthService;
import com.education.platform.service.RedisService;
import com.education.platform.common.ResultCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
public class AuthServiceImpl implements AuthService {

    private final UserMapper userMapper;
    private final UserAuthLogMapper userAuthLogMapper;
    private final SmsCodeMapper smsCodeMapper;
    private final RedisService redisService;

    private static final String LOGIN_FAIL_KEY = "login_fail:";
    private static final int MAX_LOGIN_FAIL = 5;
    private static final int LOGIN_LOCK_TIME = 30; // 分钟

    @Override
    @Transactional
    public AuthTokenVO register(RegisterDTO registerDTO, String clientInfo) {
        // 检查用户是否已存在
        User existUser = userMapper.selectOne(
            new LambdaQueryWrapper<User>()
                .eq(User::getMobile, registerDTO.getMobile())
        );
        if (existUser != null) {
            throw new BusinessException(ResultCode.USER_ALREADY_EXISTS);
        }

        // 创建用户
        User user = new User();
        user.setUsername(registerDTO.getUsername());
        user.setMobile(registerDTO.getMobile());
        user.setPassword(BCrypt.hashpw(registerDTO.getPassword()));
        user.setRoles(registerDTO.getRole());
        user.setStatus("ACTIVE");

        userMapper.insert(user);

        // 记录日志
        recordAuthLog(user.getId(), "REGISTER", clientInfo, "SUCCESS", null);

        // 生成token
        StpUtil.login(user.getId());
        String token = StpUtil.getTokenValue();

        return AuthTokenVO.builder()
                .token(token)
                .userId(user.getId())
                .username(user.getUsername())
                .roles(Arrays.asList(user.getRoles().split(",")))
                .build();
    }

    @Override
    public AuthTokenVO login(LoginDTO loginDTO, String clientInfo) {
        String lockKey = LOGIN_FAIL_KEY + loginDTO.getMobile();

        // 检查登录失败次数
        Integer failCount = redisService.get(lockKey);
        if (failCount != null && failCount >= MAX_LOGIN_FAIL) {
            throw new BusinessException(ResultCode.LOGIN_LOCKED);
        }

        // 查找用户
        User user = userMapper.selectOne(
            new LambdaQueryWrapper<User>()
                .eq(User::getMobile, loginDTO.getMobile())
        );

        if (user == null || !BCrypt.checkpw(loginDTO.getPassword(), user.getPassword())) {
            // 增加失败次数
            redisService.increment(lockKey, LOGIN_LOCK_TIME * 60);
            recordAuthLog(user != null ? user.getId() : null, "LOGIN", clientInfo, "FAILED", "密码错误");
            throw new BusinessException(ResultCode.LOGIN_FAILED);
        }

        if (!"ACTIVE".equals(user.getStatus())) {
            recordAuthLog(user.getId(), "LOGIN", clientInfo, "FAILED", "账户被禁用");
            throw new BusinessException(ResultCode.USER_DISABLED);
        }

        // 登录成功，清除失败记录
        redisService.delete(lockKey);

        // 更新最后登录时间
        user.setLastLoginTime(LocalDateTime.now());
        userMapper.updateById(user);

        // 记录成功日志
        recordAuthLog(user.getId(), "LOGIN", clientInfo, "SUCCESS", null);

        // 生成token
        StpUtil.login(user.getId());
        String token = StpUtil.getTokenValue();

        return AuthTokenVO.builder()
                .token(token)
                .userId(user.getId())
                .username(user.getUsername())
                .roles(Arrays.asList(user.getRoles().split(",")))
                .build();
    }

    @Override
    public AuthTokenVO refreshToken() {
        Long userId = StpUtil.getLoginIdAsLong();
        User user = userMapper.selectById(userId);

        if (user == null || !"ACTIVE".equals(user.getStatus())) {
            throw new BusinessException(ResultCode.USER_NOT_FOUND);
        }

        // 刷新token
        StpUtil.renewTimeout(7200); // 延长2小时
        String token = StpUtil.getTokenValue();

        return AuthTokenVO.builder()
                .token(token)
                .userId(user.getId())
                .username(user.getUsername())
                .roles(Arrays.asList(user.getRoles().split(",")))
                .build();
    }

    @Override
    @Transactional
    public void changePassword(ChangePasswordDTO changePasswordDTO) {
        Long userId = StpUtil.getLoginIdAsLong();
        User user = userMapper.selectById(userId);

        if (user == null) {
            throw new BusinessException(ResultCode.USER_NOT_FOUND);
        }

        // 验证原密码
        if (!BCrypt.checkpw(changePasswordDTO.getOldPassword(), user.getPassword())) {
            recordAuthLog(userId, "CHANGE_PASSWORD", null, "FAILED", "原密码错误");
            throw new BusinessException(ResultCode.OLD_PASSWORD_ERROR);
        }

        // 检查新密码不能与原密码相同
        if (BCrypt.checkpw(changePasswordDTO.getNewPassword(), user.getPassword())) {
            throw new BusinessException("新密码不能与原密码相同");
        }

        // 更新密码
        user.setPassword(BCrypt.hashpw(changePasswordDTO.getNewPassword()));
        userMapper.updateById(user);

        // 记录成功日志
        recordAuthLog(userId, "CHANGE_PASSWORD", null, "SUCCESS", null);
        
        // 修改密码后，踢出当前登录，强制重新登录
        StpUtil.logout(userId);
        
        log.info("用户{}修改密码成功，已强制退出登录", userId);
    }

    @Override
    @Transactional
    public void forgotPassword(ForgotPasswordDTO forgotPasswordDTO) {
        User user = userMapper.selectOne(
            new LambdaQueryWrapper<User>()
                .eq(User::getMobile, forgotPasswordDTO.getMobile())
        );

        if (user == null) {
            throw new BusinessException(ResultCode.USER_NOT_FOUND);
        }

        user.setPassword(BCrypt.hashpw(forgotPasswordDTO.getNewPassword()));
        userMapper.updateById(user);

        recordAuthLog(user.getId(), "RESET_PASSWORD", null, "SUCCESS", null);
    }

    private void validateSmsCode(String mobile, String code, String type) {
        SmsCode smsCode = smsCodeMapper.selectOne(
            new LambdaQueryWrapper<SmsCode>()
                .eq(SmsCode::getMobile, mobile)
                .eq(SmsCode::getCode, code)
                .eq(SmsCode::getType, type)
                .eq(SmsCode::getUsed, false)
                .gt(SmsCode::getExpireTime, LocalDateTime.now())
                .orderByDesc(SmsCode::getCreatedAt)
                .last("LIMIT 1")
        );

        if (smsCode == null) {
            throw new BusinessException(ResultCode.SMS_CODE_INVALID);
        }

        // 标记验证码已使用
        smsCode.setUsed(true);
        smsCodeMapper.updateById(smsCode);
    }

    private void recordAuthLog(Long userId, String action, String clientInfo, String result, String remark) {
        UserAuthLog log = new UserAuthLog();
        log.setUserId(userId);
        log.setAction(action);
        log.setResult(result);
        log.setRemark(remark);

        if (StrUtil.isNotBlank(clientInfo)) {
            String[] parts = clientInfo.split("\\|");
            if (parts.length >= 1) {
                log.setIp(parts[0]);
            }
            if (parts.length >= 2) {
                log.setUserAgent(parts[1]);
            }
        }

        userAuthLogMapper.insert(log);
    }
}
